package store.lunangangster.nodeflow.parser.base;

import static store.lunangangster.nodeflow.enums.FlowType.FLOW_ELSE;
import static store.lunangangster.nodeflow.enums.FlowType.FLOW_IF;
import static store.lunangangster.nodeflow.enums.FlowType.FLOW_WHEN;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.sun.istack.internal.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Element;
import store.lunangangster.nodeflow.builder.FlowBuilder;
import store.lunangangster.nodeflow.builder.entity.ChainProp;
import store.lunangangster.nodeflow.common.BaseConstant;
import store.lunangangster.nodeflow.enums.FlowType;
import store.lunangangster.nodeflow.exception.RuleSourceParseException;
import store.lunangangster.nodeflow.parser.base.expression.ExpressionParserHelper;
import store.lunangangster.nodeflow.parser.helper.ParserHelper;
import store.lunangangster.nodeflow.rt.element.Executable;
import store.lunangangster.nodeflow.rt.element.flow.Flow;
import store.lunangangster.nodeflow.rt.element.flow.IfFlow;
import store.lunangangster.nodeflow.util.ExecutableUtil;

/**
 * 适用于Xml文件中的基础解析器，适用于后缀为.xml的规则文件
 * @author cwk
 * @version 1.0
 */
public class BaseXmlFlowParser extends XmlFlowParser {

	/**
	 * 解析单个以XML表示的chain
	 * @param chainElement 单个XML表示的chain
	 * @return 解析出的ChainProp中间对象
	 * @throws RuleSourceParseException	解析错误时抛出
	 */
	@Override
	public ChainProp parseChain(Element chainElement) throws RuleSourceParseException {
		// "id"属性不可为空
		if (chainElement.attribute(BaseConstant.ID) == null) {
			throw new RuleSourceParseException(String.format("Chain parse error: chain have no attribute \"%s\"", BaseConstant.ID));
		}
		String chainId = chainElement.attribute(BaseConstant.ID).getText();
		// 使用中间对象而不是创建一个Chain，防止解析中出现异常而导致需要从FlowBus中将Chain移除
		ChainProp chain = new ChainProp(chainId, new ArrayList<>());
		// 1. 解析<chain/>
		List<Element> elements = chainElement.elements();
		int elementCount = elements.size();
		for (int i = 0; i < elementCount; i++) {
			Element element = elements.get(i);
			String flowName = element.getName();
			FlowType flowType = FlowType.getFlowType(flowName);
			if (flowType == null) {
				throw new RuleSourceParseException(String.format("Chain parse error: undefined flowType for name: %s", flowName));
			}
			// 不可单独出现的FlowType，则跳过解析；如：FLOW_ELSE
			if (flowType.getClazz() == null) {
				continue;
			}
			// 解析单闭合或双闭合标签，会得到一个Flow
			Flow flow = parseFlow(element);
			// 如果解析出空Flow，则自动跳过
			if (flow.isEmpty()) {
				continue;
			}
			// 如果出现了<if/>或<if></if>，则可能需要尝试读取后续可能存在的<else>或<else></else>
			if (FLOW_IF.equals(flowType)) {
				IfFlow ifFlow = (IfFlow) flow;
				// 如果false分支为空，并且后续有else，则尝试解析else标签
				// 否则即使后续有else标签，也忽略
				if (ExecutableUtil.isEmpty(ifFlow.getFalseBranch()) && i+1 < elementCount
						&& FLOW_ELSE.equals(FlowType.getFlowType(elements.get(i+1).getName()))) {
					Element elseElement = elements.get(i+1);
					// 解析else标签
					ifFlow.setFalseBranch(parseFlow(elseElement));
					// 解析成功则跳过下一个else
					i = i+1;
				}
			}
			// 考虑相邻ThenFlow的逻辑合并
			if (!ParserHelper.mergeFlow(CollUtil.getLast(chain.getFlowList()), flow)) {
				// 否则添加Flow
				chain.addFlow(flow);
			}
		}
		return chain;
	}

	/**
	 * 解析以单个{@link Element}所表示的{@link Flow}
	 * @param element	Xml元素对象
	 * @return				单个Flow对象
	 * @throws RuleSourceParseException	单个元素解析错误
	 */
	private static Flow parseFlow(@NotNull Element element) throws RuleSourceParseException {
		Assert.notNull(element);
		return isSelfClosure(element) ? parseSelfClosure(element) : parseDoubleClosure(element);
	}

	/**
	 * 解析单标签形式的单个{@link Element}所表示的{@link Flow}
	 * @param element	单标签Xml元素对象
	 * @return				单个Flow对象
	 */
	private static boolean isSelfClosure(@NotNull Element element) {
		return CollUtil.isEmpty(element.elements());
	}

	/**
	 * 解析Xml中的单标签节点
	 * @param element                    Xml元素节点
	 * @return                           可执行Flow对象
	 * @throws RuleSourceParseException  当Xml元素节点无对应的FlowType时
	 */
	private static Flow parseSelfClosure(@NotNull Element element)
			throws RuleSourceParseException {
		// 单标签必须有value属性，否则就会被忽略
		if (element.attribute(BaseConstant.VALUE) == null || StrUtil.isBlank(element.attribute(BaseConstant.VALUE).getText())) {
			return Flow.EMPTY_FLOW;
		}
		String flowValue = element.attribute(BaseConstant.VALUE).getText();
		String flowName = element.getName();
		FlowType flowType = FlowType.getFlowType(flowName);
		if (flowType == null) {
			throw new RuleSourceParseException(String.format("Chain parse error: undefined flowType for name: %s", flowName));
		}
		// 解析"value"属性
		List<Executable> executableList = ExpressionParserHelper.resolveValue(flowValue);
		Map<String, String> attributeMap = new HashMap<>(element.attributes().size());
		element.attributes().forEach(attribute -> {
			attributeMap.put(attribute.getName(), attribute.getText());
		});
		return FlowBuilder.buildFlow(flowType, executableList, attributeMap, false, true);
	}

	/**
	 * 解析Xml中的双标签节点
	 * @param element                    Xml元素节点
	 * @return                           可执行Flow对象
	 * @throws RuleSourceParseException  当Xml元素节点无对应的FlowType时
	 */
	private static Flow parseDoubleClosure(@NotNull Element element) throws RuleSourceParseException {
		String flowName = element.getName();
		FlowType flowType = FlowType.getFlowType(flowName);
		if (flowType == null) {
			throw new RuleSourceParseException(String.format("Chain parse error: undefined flowType for name: %s", flowName));
		}
		List<Executable> flowList = new ArrayList<>(element.elements().size());
		for (Element ele : element.elements()) {
			// 子调用
			Flow subFlow = parseFlow(ele);
			// 考虑相邻Then的逻辑合并
			// 注意只有外层为When时不能合并
			if (FLOW_WHEN.equals(flowType) ||
					!ParserHelper.mergeFlow((Flow) CollUtil.getLast(flowList), subFlow)) {
				flowList.add(subFlow);
			}
		}
		Map<String, String> attributeMap = new HashMap<>(element.attributes().size());
		element.attributes().forEach(attribute -> {
			attributeMap.put(attribute.getName(), attribute.getText());
		});
		return FlowBuilder.buildFlow(flowType, flowList, attributeMap, false,false);
	}
}
